﻿using BCCommon.Enums;
using BCCommon.Exceptions;
using BCCommon.Extensions;
using BCCommon.PaymentEnums;
using BCData.Common.Payment.Payment;
using BCData.Common.Payment.WxPayBill;
using BCDto.Common.Payment.Payment;
using BCDto.Common.Payment.PaymentRecord;
using BCDto.Sim.Admin.Admin;
using BCDto.Website.User;
using BCEntity.Common.Payment.Payment;
using BCService.Common.Payment.Payment.WeixinPay;
using BCService.Common.Wallet;
using BCService.Utils;
using BCService.Website.User;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using WxPayAPI;

namespace BCService.Common.Payment.Payment
{
    public class WeixinPayService : IWeixinService
    {
        private readonly IConfiguration configuration;
        private readonly IPaymentRecordData paymentRecordData;
        private readonly IUserService userService;
        private readonly IWalletService walletService;
        private readonly IWxPayBillData wxPayBillData;
        private readonly IWeixinPayHelper weixinPayHelper;
        private readonly IWeiXinPayPlug weiXinPayPlug;
        public PaymentChannelType ChannelType { get; set; }

        public SourceAppType AppType { get; set; }

        public SourceOSType OSType { get; set; }

        public WeixinPayService(
            IConfiguration configuration,
            IPaymentRecordData paymentRecordData,
            IWxPayBillData wxPayBillData,
            IUserService userService,
            IWalletService walletService,
            IWeixinPayHelper weixinPayHelper)
        {
            this.configuration = configuration;
            this.paymentRecordData = paymentRecordData;
            this.wxPayBillData = wxPayBillData;
            this.userService = userService;
            this.walletService = walletService;
            this.weixinPayHelper = weixinPayHelper;
            this.weiXinPayPlug = this.weixinPayHelper.GetWeiXinPayPlug();
        }

        public decimal? GetBalance(UserDto userDto)
        {
            return null;
        }

        public decimal? GetLimit(UserDto userDto)
        {
            return null;
        }

        public decimal? GetAgentBalance(long agentRecordId)
        {
            return null;
        }

        public decimal? GetAgentLimit(long agentRecordId)
        {
            return null;
        }

        public PaymentDto TryPay(SourceAppType appType, FlowType orderType, string subject, string referenceNumber, SourceOSType osType, SourceDeviceType deviceType, string deviceId, IDictionary<string, string> parameters, decimal amount, SourcePayerType? payer,UserDto userDto)
        {
            decimal newAmount = Math.Round(amount, 2);

            PaymentRecordEntity entity = this.paymentRecordData.Create(this.ChannelType, orderType.ToString(),subject, referenceNumber, newAmount, 
                userDto.UserId, appType, osType, payer, configuration[$"WeixinPay:{appType}:APP_ID"]);

            this.paymentRecordData.SetAsRequireAction(entity.PaymentRecordId);

            return new PaymentDto
            {
                PaymentRecordId = entity.PaymentRecordId,
                OrderType = orderType,
                ReferenceNumber = referenceNumber,
                PayChannelType = this.ChannelType,
                OriginalAmount = newAmount,
                Discount = 0,
                PaidAmount = newAmount,
                Result = PaymentResultType.RequireAction,
                ActionArguments = JsonConvert.SerializeObject(weixinPayHelper.CreateTryPay(appType, osType, this.ChannelType, userDto.UserId, entity.PaymentRecordId, subject, newAmount, orderType, referenceNumber, "APP", userDto.AddressIp))
            };
        }


        public PaymentDto AgentTryPay(SourceAppType appType, FlowType orderType, string subject, string referenceNumber, 
            SourceOSType osType, SourceDeviceType deviceType, string deviceId, IDictionary<string, string> parameters, 
            decimal amount, SourcePayerType? payer, AdminDto admin)
        {
            decimal newAmount = Math.Round(amount, 2);

            PaymentRecordEntity entity = this.paymentRecordData.Create(this.ChannelType, orderType.ToString(), subject, referenceNumber, newAmount,
                null, appType, osType, payer, configuration[$"WeixinPay:{appType}:APP_ID"], admin.AdminId, admin.FullName);

            this.paymentRecordData.SetAsRequireAction(entity.PaymentRecordId);

            return new PaymentDto
            {
                PaymentRecordId = entity.PaymentRecordId,
                OrderType = orderType,
                ReferenceNumber = referenceNumber,
                PayChannelType = this.ChannelType,
                OriginalAmount = newAmount,
                Discount = 0,
                PaidAmount = newAmount,
                Result = PaymentResultType.RequireAction,
                ActionArguments = JsonConvert.SerializeObject(weixinPayHelper.CreateTryPay(appType, osType, this.ChannelType, 0, entity.PaymentRecordId, subject, newAmount, orderType, referenceNumber, "NATIVE", admin.AddressIp))
            };
        }

        public PaymentQRCodeDto GenerateQRCode(SourceAppType appType, FlowType orderType, string subject, string referenceNumber, SourceOSType osType, SourceDeviceType deviceType, string deviceId, IDictionary<string, string> parameters, decimal amount, SourcePayerType? payer,UserDto userDto)
        {
            decimal newAmount = Math.Round(amount, 2);

            PaymentRecordEntity entity = this.paymentRecordData.Create(this.ChannelType, orderType.ToString(),subject, referenceNumber, newAmount, userDto.UserId, appType, osType, payer, configuration[$"WeixinPay:{appType}:APP_ID"]);

            var dic = weixinPayHelper.CreatQRCode(appType, osType, this.ChannelType, userDto.UserId, entity.PaymentRecordId, subject, newAmount, orderType, referenceNumber, "NATIVE", userDto.AddressIp);

            if (dic["return_code"].ToString() == "SUCCESS")
            {
                this.paymentRecordData.SetAsRequireAction(entity.PaymentRecordId);
                return new PaymentQRCodeDto
                {
                    PayChannelType = this.ChannelType,
                    Token = dic["code_url"].ToString(),
                    ExpireTime = DateTime.Now.AddHours(2),
                    OutTradeNo = entity.PaymentRecordId.ToString()
                };
            }
            else
            {
                this.paymentRecordData.SetAsFailed(entity.PaymentRecordId, dic["message"].ToString());

                throw new ArgumentException("无法生成支付二维码。");
            }
        }

        public PaymentQRCodeDto AgentGenerateQRCode(SourceAppType appType, FlowType orderType, string subject,
    string referenceNumber, SourceOSType osType, SourceDeviceType deviceType, string deviceId,
    IDictionary<string, string> parameters, decimal amount, SourcePayerType? payer, AdminDto admin)
        {
            decimal newAmount = Math.Round(amount, 2);

            PaymentRecordEntity entity = this.paymentRecordData.Create(this.ChannelType, orderType.ToString(), 
                subject, referenceNumber, newAmount, null, appType, osType, payer,
                configuration[$"WeixinPay:{appType}:APP_ID"], admin.AdminId, admin.FullName);

            var dic = weixinPayHelper.CreatQRCode(appType, osType, this.ChannelType, 0, 
                entity.PaymentRecordId, subject, newAmount, orderType, referenceNumber, "NATIVE", 
                admin.AddressIp);

            if (dic["return_code"].ToString() == "SUCCESS")
            {
                this.paymentRecordData.SetAsRequireAction(entity.PaymentRecordId);
                return new PaymentQRCodeDto
                {
                    Amount = newAmount,
                    PayChannelType = this.ChannelType,
                    Token = dic["code_url"].ToString(),
                    ExpireTime = DateTime.Now.AddHours(2),
                    OutTradeNo = entity.PaymentRecordId.ToString()
                };
            }
            else
            {
                this.paymentRecordData.SetAsFailed(entity.PaymentRecordId, dic["message"].ToString());

                throw new ArgumentException("无法生成支付二维码。");
            }
        }

        public decimal Refund(long adminId, string adminName, long recordId, decimal refundAmount)
        {
            PaymentRecordEntity payment = this.paymentRecordData.GetPaymentRecordByPaymentRecordId(recordId);

            if (payment == null)
            {
                throw new ArgumentException("无法找到匹配的付款记录，无法进行退款。");
            }

            decimal refundedAmount = this.paymentRecordData.GetPaymentRecordsByReferenceNumber(payment.OrderType.AsEnum<FlowType>(), payment.ReferenceNumber).Where(p => p.Status == PaymentResultType.Success.ToString() && p.Amount < 0).Sum(p => p.Amount);

            if ((-refundedAmount + refundAmount) > payment.Amount)
            {
                throw new ArgumentException("可退余额不足，无法进行退款。");
            }

            decimal newAmount = Math.Round(refundAmount, 2);

            PaymentRecordEntity refund = this.paymentRecordData.Create(this.ChannelType, payment.OrderType.ToString(),"退款", payment.ReferenceNumber, -newAmount, payment.UserId, payment.AppType.AsEnum<SourceAppType>(), payment.OSType.AsEnum<SourceOSType>(), (SourcePayerType?)payment.Payer, payment.AppId, adminId, adminName, PaymentOperationType.Refund.ToString(), SourceFlowDirectionType.Outlay);

            var dictionary = weixinPayHelper.Refund(this.ChannelType, payment.UserId.Value, payment.AppType.AsEnum<SourceAppType>(), payment.OSType.AsEnum<SourceOSType>(), payment.PaymentRecordId, refund.PaymentRecordId.ToString(), newAmount, payment.Amount);

            if (dictionary["return_code"].ToString() == "SUCCESS")
            {
                this.paymentRecordData.SetAsSuccess(refund.PaymentRecordId);

                return payment.Amount - -refundedAmount - refundAmount;
            }
            else
            {
                this.paymentRecordData.SetAsFailed(refund.PaymentRecordId, dictionary["message"].ToString());

                throw new ArgumentException("将退款请求提交到微信时发生错误，你可能需要先通过微信支付商户后台确认退款状态。");
            }
        }

        public PaymentRecordDto CheckOrder(SourceAppType appType, long paymentRecordId)
        {
            var entity = this.paymentRecordData.GetPaymentRecordByPaymentRecordId(paymentRecordId);
            if (entity != null)
            {
                var dictionary = weixinPayHelper.OrderQuery(appType, entity.PaymentRecordId);
                //return_code 和result_code都为SUCCESS的时候有返回
                if (dictionary["return_code"].ToString() == "SUCCESS" && dictionary["result_code"].ToString() == "SUCCESS")
                {
                    if (dictionary["trade_state"].ToString() == "SUCCESS")
                    {
                        //验证支付金额是否一致和结果
                        decimal amount = decimal.Parse(dictionary["total_fee"].ToString()) / 100; //微信支付单位为分，转换为元
                        return amount == entity.Amount ? this.paymentRecordData.SetAsSignTime(entity.PaymentRecordId).As<PaymentRecordDto>() : null;
                    }
                }
            }
            return null;
        }

        /// <summary>
        /// 结果通知
        /// </summary>
        /// <param name="parameters">接收参数字典</param>
        /// <param name="payment">支付信息</param>
        /// <returns></returns>
        public ReceiveNotificationDto ReceiveNotification(IDictionary<string, string> parameters, out PaymentDto payment)
        {
            if (!parameters.ContainsKey("Request-Body"))
            {
                throw new ArgumentException("通知数据格式不正确。");
            }

            weiXinPayPlug.AppType = this.AppType;
            
            var dictionary = weiXinPayPlug.FromXmlToDictionary(parameters["Request-Body"]).ToDictionary(p => p.Key, p => p.Value.ToString());

            if (dictionary["return_code"] != "SUCCESS")
            {
                throw new ArgumentException("服务器只能处理返回码为 SUCCESS 的付款通知。");
            }

            long paymentRecordId = 0;
            decimal amount = 0;

            if (dictionary.Any(p => p.Key == "req_info"))
            {
                WeixinPayRefundNotifyService weixinPayRefundNotifyService = new WeixinPayRefundNotifyService();

                string refundValue = weixinPayRefundNotifyService.ProcessNotify(parameters["Request-Body"], configuration["WeixinPay:DriverApp:KEY"]);
                dictionary = weiXinPayPlug.FromXmlToDictionary(refundValue).ToDictionary(p => p.Key, p => p.Value.ToString());

                paymentRecordId = long.Parse(dictionary["out_refund_no"]);
                amount = decimal.Parse(dictionary["total_fee"]) / 100; //微信支付单位为分，转换为元
            }
            else
            {
                paymentRecordId = long.Parse(dictionary["out_trade_no"]);
                amount = decimal.Parse(dictionary["total_fee"]) / 100; //微信支付单位为分，转换为元
            }

            PaymentRecordEntity entity = this.paymentRecordData.GetPaymentRecordByPaymentRecordId(paymentRecordId);

            if (entity == null)
            {
                throw new ArgumentException("找不到指定的付款订单。");
            }

            if (Math.Abs(entity.Amount) != amount)
            {
                throw new ArgumentException("通知的金额与订单金额不符。");
            }

            // 异步回调不能修改验签时间，需要和同步验签的逻辑相反。否则数据会重复。
            entity = this.paymentRecordData.SetAsSuccess(entity.PaymentRecordId);

            payment = entity.TransformTo<PaymentDto>();

            // 微信支付需要返回指定格式的 XML 表示成功的处理了通知。
            return new ReceiveNotificationDto
            {
                StatusCode = 200,
                ContentType = "application/xml",
                ReturnString = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"
            };
        }

        /// <summary>
        /// 提现转账
        /// </summary>
        /// <param name="appType">App类型</param>
        /// <param name="osType">系统类型</param>
        /// <param name="recordId">记录Id</param>
        /// <param name="transferAmount">提现转账金额</param>
        /// <param name="userId">用户Id</param>
        /// <param name="open_userid">目标用户Id</param>
        /// <param name="userDto"></param>
        /// <returns></returns>
        public decimal Transfer(SourceAppType appType, SourceOSType osType, long recordId, decimal transferAmount, long userId, string open_userid,UserDto userDto)
        {
            var wallet = walletService.GetWalletByUserId(userId);
            if (wallet.WxPayAuthInfo == null)
            {
                throw new ArgumentException("当前提现用户未授权，非法操作！");
            }
            //验证是否在钱包授权过
            dynamic wallet_result = JsonConvert.DeserializeObject(wallet.WxPayAuthInfo.ToString());

            //如未授权说明非法提现
            string weixin_user_id = wallet_result.openid;
            if (weixin_user_id == open_userid)
            {
                var result = weixinPayHelper.Transfers(appType, recordId, open_userid, transferAmount, userDto.AddressIp);
                //企业付款成功时间 不成功不存在该时间
                if (result.TryGetValue("payment_time", out _))
                {
                    return transferAmount;
                }
            }
            throw new ArgumentException("当前提现用户和授权用户不一致，非法操作！");
        }

        /// <summary>
        /// 返回客户端发起授权的配置信息
        /// </summary>
        /// <param name="appType"></param>
        /// <param name="osType"></param>
        /// <param name="userDto"></param>
        /// <returns></returns>
        public IDictionary<string, string> CreateAppAuthLogin(SourceAppType appType, SourceOSType osType,UserDto userDto)
        {
            //微信客户端不需要配置信息
            return new Dictionary<string, string>
            {
                { "auth_info", "" }
            };
        }

        /// <summary>
        /// 授权登录信息
        /// </summary>
        /// <param name="auth_code">授权码</param>
        /// <param name="appType">App类型</param>
        /// <param name="osType">系统类型</param>
        /// <param name="userDto"></param>
        /// <returns></returns>
        public IDictionary<string, object> AuthLoginInfo(string auth_code, SourceAppType appType, SourceOSType osType,UserDto userDto)
        {
            string jsonString = weixinPayHelper.GetAuthInfo(appType, auth_code);
            //更新钱包授权信息
            walletService.ChangeWxPayAuthInfo(userDto.UserId, jsonString);
            return new Dictionary<string, object>
            {
                { "auth_login_info",  JsonConvert.DeserializeObject(jsonString)},
                { "channel_id" , ChannelType.ToString() }
            };
        }

        /// <summary>
        /// 确认支付效验
        /// </summary>
        /// <param name="paymentRecordId"></param>
        /// <param name="referenceNumber"></param>
        /// <param name="password"></param>
        /// <param name="userDto"></param>
        /// <returns></returns>
        public bool ConfirmPayValidation(long paymentRecordId, string referenceNumber, string password, UserDto userDto)
        {
            throw new ArgumentException("确认支付仅支持线下支付、余额支付及银行卡支付");
        }

        /// <summary>
        /// 同步验签
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public PaymentRecordDto SyncCheckSign(IDictionary<string, string> parameters)
        {
            if (!parameters.Keys.Contains("SourceAppType"))
            {
                throw new ArgumentException("微信验签，参数中应包含SourceAppType");
            }
           
            if (!parameters.Keys.Contains("PaymentRecordId"))
            {
                throw new ArgumentException("微信验签，参数中应包含PaymentRecordId");
            }

            if (!long.TryParse(parameters["PaymentRecordId"], out long paymentRecordId))
            {
                throw new ArgumentException("支付记录Id不允许为空");
            }

            return this.Synchronize(paymentRecordId, null);

            //return this.CheckOrder(parameters["SourceAppType"].AsEnum<SourceAppType>(), paymentRecordId);
        }

        /// <summary>
        /// 下载账单
        /// </summary>
        /// <param name="billDate">对账单日期</param>
        /// <param name="billType">账单类型</param>
        /// <returns></returns>
        public string DownloadBill(DateTime billDate, WeChatBillType billType)
        {
            Log.Info("DownloadBill", "DownloadBill is processing...");

            WxPayData data = new WxPayData();
            data.SetValue("bill_date", billDate.ToString("yyyyMMdd"));//账单日期
            data.SetValue("bill_type", billType.ToString());//账单类型
            WxPayData result = WxPayApi.DownloadBill(data);//提交下载对账单请求给API，接收返回结果
            SortedDictionary<string, object> valueDic = result.GetValues();

            if (valueDic.Keys.Contains("error_code"))
            {
                return string.Empty;
            }
            Log.Info("DownloadBill", "DownloadBill process complete, result : " + result.ToXml());
            return valueDic["result"].ToString();
        }

        public PaymentRecordDto Synchronize(long paymentRecordId, PaymentSynchronizeRequestDto requestDto)
        {
            var entity = this.paymentRecordData.GetPaymentRecordByPaymentRecordId(paymentRecordId);

            if (entity == null)
            {
                throw new ObjectNotFoundException();
            }

            if (entity.Status != PaymentResultType.Success.ToString() && entity.CheckSignTime == null)
            {
                var dictionary = weixinPayHelper.OrderQuery(entity.AppType.AsEnum<SourceAppType>(), entity.PaymentRecordId);

                if (dictionary["return_code"].ToString() == "SUCCESS" &&
                    dictionary["result_code"].ToString() == "SUCCESS" &&
                    dictionary["trade_state"].ToString() == "SUCCESS" &&
                    decimal.Parse(dictionary["total_fee"].ToString()) / 100 == entity.Amount)
                {
                    entity = this.paymentRecordData.SetAsSuccess(entity.PaymentRecordId);
                }
            }

            return entity.TransformTo<PaymentRecordDto>();
        }
    }
}